package com.googlecode.cswish.struts.spring;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;

import com.googlecode.cswish.struts.hibernate.DbTools;

/**
 * Project structure, Runtime structure
 * 
 * @author Jerry
 * 
 */
public class SystemEnv {
	
	public static final String JETTY_HOME = "JETTY_HOME";
	public static final String CSWISH_HOME = "CSWISH_HOME";
	public static final String DB_HOME = "DB_HOME";
	public static final String JUNIT_TEST = "JUNIT_TEST"; // ObjectFactory
	public static final String RUNNING_IN_ECLIPSE = "RUNNING_IN_ECLIPSE";
	public static final String PROJECT_NAME = "PROJECT_NAME";
	public static final String PROJECT_HOME = "PROJECT_HOME";
	public static final String PLUGIN_HOME = "PLUGIN_HOME";
	public static final String STARTUP_TIME = "STARTUP_TIME";

	public static final String RUNNING_MODEL = "RUNNING_MODEL";
	public static final String RUNNING_MODEL_SMALL = "1";
	public static final String RUNNING_MODEL_NORMAL = "2";
	public static final String RUNNING_MODEL_HUGE = "3";

	private static final Log logger = LogFactory.getLog(SystemEnv.class);
	private static boolean inited = false;

	public static void init() {
		if (inited) {
			return;
		}

		inited = true;
		new SystemEnv()._init();
	}

	private void _init() {
		loadDefaultProperties();
		
		initProjectEnv();
		tryDefaultLog4jFileDir();
		initBitronixLog();

		boolean isEclipseEnv = "true".equals(System
				.getProperty(RUNNING_IN_ECLIPSE));
		if (isEclipseEnv) {
			checkAndPrepareRuntimeEnv();
		}

		DbTools.getInstance().checkAndStartupDb();
	}

	private void checkAndPrepareRuntimeEnv() {
		String installerPath = new File(System.getProperty(CSWISH_HOME))
				.getParentFile().getPath();
		
		// 1. check the espirit folder
		String dbHomeDir = System.getProperty(DB_HOME);
		if (!new File(dbHomeDir).exists()) {
			String installerZipPath = installerPath + "/package/installer.zip";
			String targetDir = installerPath + "/work/";
			File installerZipFile = new File(installerZipPath);
			if (installerZipFile.exists()) {
				unzip(installerZipFile, null);
				
				String srcDir = installerPath + "/package-patch/";
				try {
					FileUtils.copyDirectory(new File(srcDir), new File(targetDir));
				} catch (Exception ex) {
					logger.error("Failed to copy the patch", ex);
				}
			}
		}
		
		// 2. check the init sql
		File patchSqlDir = new File(installerPath + "/package-patch/mysql/backup/");
		if (patchSqlDir.exists()) {
			for (File sqlFile : patchSqlDir.listFiles()) {
				String sqlFileName = sqlFile.getName();
				if (sqlFile.isFile() && sqlFileName.endsWith("-init.sql")) {
					File targetSqlFile = new File(installerPath + "/work/mysql/backup/" + sqlFileName);
					if (!targetSqlFile.exists() || sqlFile.lastModified() > targetSqlFile.lastModified()) {
						try {
//							int index = sqlFileName.indexOf("-");
//							String dbName = sqlFileName.substring(0, index);
							
//							if (DbTools.getInstance().dbIsReady(dbName)) {
//								if (logger.isWarnEnabled()) {
//									logger.warn("Found new sql file: " + sqlFile.getPath() + "! Please delete the old database '"
//											+ dbName + "' firstly");
//								}
//							}
							// copy it directly, the new site need use the new init script
							FileUtils.copyFile(sqlFile, targetSqlFile);
						} catch (IOException ex) {
							logger.error("Failed to update sql file: " + sqlFile.getPath(), ex);
						}
					}
				}
			}
		}
	}

	private boolean unzip(File srcZipFile, String targetDir) {
		if (logger.isDebugEnabled()) {
			logger.debug("Begin unzip file: " + srcZipFile.getPath());
		}
		if (targetDir == null) {
			targetDir = System.getProperty(CSWISH_HOME);
		}
		new File(targetDir).mkdir();

		boolean isSuccessful = true;
		try {
			InputStream bis = new BufferedInputStream(new FileInputStream(
					srcZipFile));
			ZipInputStream zis = new ZipInputStream(bis);

			OutputStream bos = null;

			// byte[] b = new byte[1024];
			ZipEntry entry = null;
			while ((entry = zis.getNextEntry()) != null) {
				String entryName = entry.getName();
				File file = new File(targetDir + entryName);

				if (entry.isDirectory()) {
					file.mkdirs();
				} else {
					bos = new FileOutputStream(targetDir + entryName);
					int len = 0;
					byte[] buf = new byte[20480]; // 20k
					while ((len = zis.read(buf)) > 0) {
						bos.write(buf, 0, len);
					}
					bos.flush();
					bos.close();
				}
			}
			zis.close();
		} catch (IOException e) {
			logger.error("Failed to unzip the install file", e);
			isSuccessful = false;
		}
		
		logger.debug("Finish unzip");
		return isSuccessful;
	}

	private void tryDefaultLog4jFileDir() {
		// log4j.appender.FILE.File
		Appender appender = Logger.getRootLogger().getAppender("FILE");
		FileAppender fileAppender = (FileAppender) appender;
		if (fileAppender != null) {
			if (fileAppender.getFile() == null) {
				String jettyHome = System.getProperty(JETTY_HOME);
				String file = jettyHome + "logs/debug.log";
				fileAppender.setFile(file);
				fileAppender.activateOptions();
			}

			// log4j.appender.PTFILE.File
			appender = Logger.getLogger("PT").getAppender("PTFILE");
			fileAppender = (FileAppender) appender;
			if (fileAppender.getFile() == null) {
				String jettyHome = System.getProperty(JETTY_HOME);
				String file = jettyHome + "logs/performance.log";
				fileAppender.setFile(file);
				fileAppender.activateOptions();
			}
		} else {
			System.err.println("Can't find the log4j property file");
		}
	}

	private Properties loadDefaultProperties() {
		// build the variable to System.property
		// it's used by
		// org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
		Properties defaultProperties = new Properties();
		InputStream input = null;
		try {
			input = SystemEnv.class.getResourceAsStream("/cswish.properties");
			defaultProperties.load(input);
		} catch (IOException ex) {
			logger.error(
					"Failed to load the default configuration: cswish.properties",
					ex);
		} finally {
			IOUtils.closeQuietly(input);
		}
		for (Entry<Object, Object> entry : defaultProperties.entrySet()) {
			String key = entry.getKey().toString();
			String value = entry.getValue().toString();
			if (System.getProperty(key) == null) {
				System.setProperty(key, value);
			}
		}
		
		// default constant, see Config
		if (System.getProperty("security.enableSaas") == null) {
			System.setProperty("security.enableSaas", "true");
		}
		return defaultProperties;
	}

	private void initProjectEnv() {
		// guess the root dir
		String path = this.getClass().getProtectionDomain().getCodeSource()
				.getLocation().getFile();
		int index = path.lastIndexOf("/WEB-INF/");
		boolean isEclipseEnv = index == -1; // use System.getenv("RUNNING_IN_ECLIPSE")?
		System.setProperty(RUNNING_IN_ECLIPSE, String.valueOf(isEclipseEnv));

		String cswishHomeDir = System.getProperty(CSWISH_HOME);
		if (cswishHomeDir == null) {
			// format the path
			path = new File(path).getPath().replace(File.separatorChar, '/') + '/';

			if (isEclipseEnv) {
				String projectHome = System.getProperty("user.dir") + '/';
				System.setProperty(PROJECT_HOME, projectHome);
				if (System.getProperty(PROJECT_NAME) == null){
					int nameBegin = projectHome.lastIndexOf(File.separatorChar, projectHome.length() - 2);
					String projectName = projectHome.substring(nameBegin + 1, projectHome.length() - 1);
					System.setProperty(PROJECT_NAME, projectName);
				}
				cswishHomeDir = new File(new File(projectHome).getParentFile(), "installer/work/").getPath() + '/';
			} else {
				int jettyHomeIndex = path.lastIndexOf("/jetty", index);
				cswishHomeDir = path.substring(0, jettyHomeIndex);
			}

			System.setProperty(CSWISH_HOME, cswishHomeDir);
		}

		String jettyHome = System.getProperty(JETTY_HOME);
		if (jettyHome == null) {
			File[] files = new File(cswishHomeDir).listFiles();
			if (files != null) {
				for (File file : files) {
					if (file.isDirectory()) {
						if (file.getName().startsWith("jetty")) {
							jettyHome = file.getPath().replace(
									File.separatorChar, '/') + '/';
						}
					}
				}
			}
			if (jettyHome == null) {
				logger.error("Failed to init the environment property: JETTY_HOME, use /jetty/ as the JETTY_HOME");
				System.setProperty(JETTY_HOME, "/jetty/");
			} else {
				System.setProperty(JETTY_HOME, jettyHome);
			}
		}

		String mysqlDir = endWithDir(cswishHomeDir) + "mysql/";
		System.setProperty(DB_HOME, mysqlDir);

		String installerPath = new File(System.getProperty(CSWISH_HOME)).getParentFile().getPath();
		String pluginHome = isEclipseEnv ? installerPath + "/package-patch/jetty/webapps/root/WEB-INF/plugins"
					: jettyHome + "webapps/root/WEB-INF/plugins";
		pluginHome = new File(pluginHome).getAbsolutePath();
		System.setProperty(PLUGIN_HOME, pluginHome);
		
		String runModel;
		if (isEclipseEnv) {
			runModel = RUNNING_MODEL_SMALL;
		} else {
			try {
				com.sun.management.OperatingSystemMXBean os = (com.sun.management.OperatingSystemMXBean) java.lang.management.ManagementFactory
						.getOperatingSystemMXBean();
				long freePhysicalSize = os.getFreePhysicalMemorySize();
				float freeSize = freePhysicalSize / 1024 / 1024; // M
				if (freeSize > 1000) { // > 1g
					if (freeSize > 4000) { // >4g
						runModel = RUNNING_MODEL_HUGE;
					} else {
						runModel = RUNNING_MODEL_NORMAL;
					}
				} else {
					runModel = RUNNING_MODEL_SMALL;
				}
			} catch (Throwable e) {
				logger.error("Can't get system memory information, treat it as normal environment");
				runModel = RUNNING_MODEL_NORMAL;
			}
		}
		System.setProperty(RUNNING_MODEL, runModel);
		
		System.setProperty(STARTUP_TIME, String.valueOf(System.currentTimeMillis()));
		// disable eclipse flag
		// System.setProperty(RUNNING_IN_ECLIPSE, "false"); // debug code
	}

	private String endWithDir(String path) {
		if (path.charAt(path.length() - 1) == '/') {
			return path;
		} else {
			return path + '/';
		}
	}

	private void initBitronixLog() {
		String jettyHome = System.getProperty(JETTY_HOME);
		System.setProperty("bitronix.tm.journal.disk.logPart1Filename",
				jettyHome + "logs/btm1.tlog");
		System.setProperty("bitronix.tm.journal.disk.logPart2Filename",
				jettyHome + "logs/btm2.tlog");
	}
}
